import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:vector_math/vector_math_64.dart';

import 'package:flutter/widgets.dart';
import '../utils/common_utils.dart';
import '../utils/ignorable_change_notifier.dart';

typedef AnimValueChangeListener = Future Function(CutCanvasValue to);

/// 通过Stream来保存页面的state，用户操作或逻辑处理修改Stream中的数据来刷新页面
///
/// controller必须在不需要使用后调用dispose方法
///
abstract class CutCanvasControllerBase<T extends CutCanvasValue> {
  /// 通常使用广播Stream
  Stream<T> get stateStream;

  T prevValue;

  T value;

  Size canvasSize;

  Size childSize;

  /// 手势平移的距离
  Offset position;

  /// 缩放值
  double scale;

  /// 由旋转引起的缩放
  double get scaleByRotate;

  /// 旋转弧度
  double rotation;

  /// 显示宽高比
  double aspectRatio;

  double photoAspectRatio;

  bool enableGestureWithoutBackground;

  bool get enableGesture;

  void reset();

  void dispose();

  void addIgnorableListener(VoidCallback callback);

  void removeIgnorableListener(VoidCallback callback);

  /// Nevermind this method :D, look away
  void setScaleInvisibly(double scale);

  void setAnimValueChangeListener(AnimValueChangeListener listener);

  /// 更新当前画布大小
  void updateCanvasSize(Size canvasSize, Size childSize);

  void updateMultiple({
    Offset position,
    double scale,
    double scaleByRotate,
    double rotation,
    Offset rotationFocusPoint,
    double aspectRatio,
  });

  void focusUpdate();
}

class CutCanvasController implements CutCanvasControllerBase<CutCanvasValue> {
  CutCanvasController({
    this.photoAspectRatio,
    Offset initialPosition = Offset.zero,
    double initialScale,
    double initialScaleByRotate,
    double initialRotation = 0.0,
    double initialAspectRatio = 1.0,
  })  : _valueNotifier = IgnorableValueNotifier(
          CutCanvasValue(
            position: initialPosition,
            rotation: initialRotation,
            scale: initialScale,
            scaleByRotate: initialScaleByRotate,
            aspectRatio: initialAspectRatio,
          ),
        ),
        super() {
//    initial = value;
//    prevValue = initial;
//
//    _valueNotifier.addListener(_changeListener);
//    _stateController.sink.add(initial);
    initialCanvas();
  }

  initialCanvas() async {
    initial = value;
    prevValue = initial;

    _valueNotifier.addListener(_changeListener);
    _stateController.sink.add(initial);
  }

  AnimValueChangeListener _animValueChangeListener;

  final IgnorableValueNotifier<CutCanvasValue> _valueNotifier;

  CutCanvasValue initial;

  StreamController<CutCanvasValue> _stateController =
      StreamController<CutCanvasValue>.broadcast();

  @override
  Stream<CutCanvasValue> get stateStream => _stateController.stream;

  @override
  CutCanvasValue prevValue;

  Size canvasSize;

  Size childSize;

  bool _enableGestureWithoutBackground = true;

  @override
  set enableGestureWithoutBackground(bool _enableGestureWithoutBackground) {
    this._enableGestureWithoutBackground = _enableGestureWithoutBackground;
    _valueNotifier.updateItself();
  }

  @override
  bool get enableGestureWithoutBackground => _enableGestureWithoutBackground;

  @override
  bool get enableGesture => false || enableGestureWithoutBackground;

  @override
  set value(CutCanvasValue newValue) {
    if (_valueNotifier.value == newValue) {
      return;
    }
    _valueNotifier.value = newValue;
  }

  @override
  CutCanvasValue get value => _valueNotifier.value;

  @override
  set position(Offset position) {
    if (value.position == position) {
      return;
    }
    prevValue = value;
    value = CutCanvasValue(
      position: position,
      scale: scale,
      scaleByRotate: scaleByRotate,
      rotation: rotation,
      aspectRatio: aspectRatio,
    );
  }

  @override
  Offset get position => value.position;

  @override
  set scale(double scale) {
    if (value.scale == scale) {
      return;
    }
    prevValue = value;
    value = CutCanvasValue(
      position: position,
      scale: scale,
      scaleByRotate: scaleByRotate,
      rotation: rotation,
      aspectRatio: aspectRatio,
    );
  }

  @override
  double get scale => value.scale;

  @override
  double get scaleByRotate => value.scaleByRotate ?? 1.0;

  /// rotation是基于当前画布（可能是旋转平移后的画布）中心点进行旋转
  /// 同时会根据角度和比例，调节由角度引起的缩放值
  @override
  set rotation(double rotation) {
    if (value.rotation == rotation) {
      return;
    }
    prevValue = value;
//    double preScaleByRotate = scaleByRotate ?? 1;
    double scaleByRotate = getRotateResizeScale(aspectRatio, rotation);

    Matrix4 _lastMatrix4 = Matrix4.identity()
      ..translate(position.dx, position.dy)
      ..rotateZ(value.rotation)
      ..scale(scale);
    Vector3 lastPoint = _lastMatrix4.transform3(Vector3(0, 0, 0));
    Matrix4 deltaMatrix = Matrix4.identity()
      ..scale(scaleByRotate / (value.scaleByRotate ?? 1.0))
      ..rotateZ(rotation - value.rotation);
    Vector3 lastOriginPoint =
        deltaMatrix.transform3(Vector3(lastPoint.x, lastPoint.y, 0));
    Offset newOffset = Offset(lastOriginPoint.x, lastOriginPoint.y);

    value = CutCanvasValue(
      position: newOffset,
      scale: scale,
      scaleByRotate: scaleByRotate,
      rotation: rotation,
      aspectRatio: aspectRatio,
    );
  }

  @override
  double get rotation => value.rotation;

  @override
  set aspectRatio(double aspectRatio) {
    aspectRatio = aspectRatio ?? photoAspectRatio;
    if (value.aspectRatio == aspectRatio) {
      return;
    }

    double scaleByRotate = getRotateResizeScale(aspectRatio, rotation);
    double scale = getInitialScale(aspectRatio);
    _animValueChangeListener?.call(value.copyWith(
      aspectRatio: aspectRatio,
      scale: scale,
      scaleByRotate: scaleByRotate,
      position: Offset.zero,
    ));
  }

  double getInitialScale(double aspectRatio) {
    if (aspectRatio > photoAspectRatio) {
      return aspectRatio / photoAspectRatio;
    } else {
      return photoAspectRatio / aspectRatio;
    }
  }

  bool isSimilarValue(double a, double b) {
    return (a - b).abs() < 0.002;
  }

  @override
  double get aspectRatio => value.aspectRatio;

  @override
  double photoAspectRatio;

  @override
  void reset() {
    value = initial;
  }

  void _changeListener() {
    _stateController.sink.add(value);
  }

  @override
  void addIgnorableListener(VoidCallback callback) {
    _valueNotifier.addIgnorableListener(callback);
  }

  @override
  void removeIgnorableListener(VoidCallback callback) {
    _valueNotifier.removeIgnorableListener(callback);
  }

  @override
  void dispose() {
    _stateController.close();
    _valueNotifier.dispose();
  }

  @override
  void setScaleInvisibly(double scale) {
    if (value.scale == scale) {
      return;
    }
    prevValue = value;
    _valueNotifier.updateIgnoring(
      CutCanvasValue(
        position: position,
        scale: scale,
        scaleByRotate: scaleByRotate,
        rotation: rotation,
        aspectRatio: aspectRatio,
      ),
    );
  }

  @override
  void updateMultiple({
    Offset position,
    double scale,
    double scaleByRotate,
    double rotation,
    Offset rotationFocusPoint,
    double aspectRatio,
  }) {
    prevValue = value;
    value = CutCanvasValue(
      position: position ?? value.position,
      scale: scale ?? value.scale,
      scaleByRotate: scaleByRotate ?? value.scaleByRotate,
      rotation: rotation ?? value.rotation,
      aspectRatio: aspectRatio ?? value.aspectRatio,
    );
  }

  @override
  void focusUpdate() {
    _valueNotifier.updateItself();
  }

  @override
  void setAnimValueChangeListener(AnimValueChangeListener listener) {
    _animValueChangeListener = listener;
  }

  @override
  void updateCanvasSize(Size canvasSize, Size childSize) {
    this.canvasSize = canvasSize;
    this.childSize = childSize;
  }
}

class CutCanvasValue {
  CutCanvasValue({
    @required this.position,
    @required this.scale,
    @required this.scaleByRotate,
    @required this.rotation,
    @required this.aspectRatio,
  });

  CutCanvasValue.empty();

  Offset position;
  double scale;

  // todo maybe null
  double scaleByRotate;

  // todo 优化将rotation拆分成两个分量（一个是刻度尺参数，一个是90度旋转参数）
  double rotation;
  double aspectRatio;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is CutCanvasValue &&
          runtimeType == other.runtimeType &&
          position == other.position &&
          scale == other.scale &&
          scaleByRotate == other.scaleByRotate &&
          rotation == other.rotation &&
          aspectRatio == other.aspectRatio;

  @override
  int get hashCode =>
      position.hashCode ^
      scale.hashCode ^
      scaleByRotate.hashCode ^
      rotation.hashCode ^
      aspectRatio.hashCode;

  static CutCanvasValue lerp(CutCanvasValue a, CutCanvasValue b, double t) {
    assert(t != null);
    if (a == null && b == null) {
      return null;
    }
    if (a == null) {
      return CutCanvasValue(
        position: t < 0.5 ? null : b.position,
        scale: t < 0.5 ? null : b.scale,
        scaleByRotate: t < 0.5 ? null : b.scaleByRotate,
        rotation: t < 0.5 ? null : b.rotation,
        aspectRatio: t < 0.5 ? null : b.aspectRatio,
      );
    }
    if (b == null) {
      return CutCanvasValue(
        position: t < 0.5 ? null : a.position,
        scale: t < 0.5 ? null : a.scale,
        scaleByRotate: t < 0.5 ? null : a.scaleByRotate,
        rotation: t < 0.5 ? null : a.rotation,
        aspectRatio: t < 0.5 ? null : a.aspectRatio,
      );
    }
    return CutCanvasValue(
      position: Offset.lerp(a.position, b.position, t),
      scale: ui.lerpDouble(a.scale ?? b.scale, b.scale ?? a.scale, t),
      scaleByRotate: ui.lerpDouble(a.scaleByRotate ?? b.scaleByRotate,
          b.scaleByRotate ?? a.scaleByRotate, t),
      rotation:
          ui.lerpDouble(a.rotation ?? b.rotation, b.rotation ?? a.rotation, t),
      aspectRatio: ui.lerpDouble(
          a.aspectRatio ?? b.aspectRatio, b.aspectRatio ?? a.aspectRatio, t),
    );
  }

  CutCanvasValue copyWith({
    Offset position,
    double scale,
    double scaleByRotate,
    double rotation,
    Offset rotationFocusPoint,
    double aspectRatio,
  }) {
    return CutCanvasValue(
      position: position ?? this.position,
      scale: scale ?? this.scale,
      scaleByRotate: scaleByRotate ?? this.scaleByRotate,
      rotation: rotation ?? this.rotation,
      aspectRatio: aspectRatio ?? this.aspectRatio,
    );
  }

  CutCanvasValue.fromJson(Map<String, dynamic> json) {
    position =
        Offset(getDouble(json['positionX']), getDouble(json['positionY']));
    scale = getDouble(json['scale']);
    scaleByRotate = getDouble(json['scaleByRotate']);
    rotation = getDouble(json['rotation']);
    aspectRatio = getDouble(json['aspectRatio']);
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = Map<String, dynamic>();
    data['positionX'] = this.position.dx.toString();
    data['positionY'] = this.position.dy.toString();
    data['scale'] = this.scale.toString();
    data['scaleByRotate'] = (this.scaleByRotate ?? 1.0).toString();
    data['rotation'] = this.rotation.toString();
    data['aspectRatio'] = this.aspectRatio.toString();
    return data;
  }

  double getDouble(dynamic value) {
    if (value is String) {
      return double.parse(value);
    } else if (value is int) {
      return value * 1.0;
    } else {
      return value;
    }
  }

  @override
  String toString() {
    return 'CutCanvasValue{position: $position, scale: $scale, scaleByRotate: $scaleByRotate, rotation: $rotation, aspectRatio: $aspectRatio}';
  }
}

class CutCanvasValueTween extends Tween<CutCanvasValue> {
  CutCanvasValueTween({CutCanvasValue begin, CutCanvasValue end})
      : super(begin: begin, end: end);

  @override
  CutCanvasValue lerp(double t) => CutCanvasValue.lerp(begin, end, t);
}
